/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.core.profiles;

import java.util.ArrayList;
import java.util.Iterator;

import org.eclipse.core.runtime.Assert;
import org.eclipse.emf.common.util.EList;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CommandStack;
import org.eclipse.jface.resource.JFaceColors;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.views.properties.tabbed.ITabbedPropertyConstants;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetWidgetFactory;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.NamedElement;
import org.eclipse.uml2.uml.Property;
import org.eclipse.uml2.uml.Stereotype;

import com.cea.papyrus.core.PapyrusPlugin;
import com.cea.papyrus.core.commands.PapyrusCommand;
import com.cea.papyrus.core.commands.StereotypePropertiesDisplayCreateCommand;
import com.cea.papyrus.core.commands.factories.DiagramElementDeleteCommandFactory;
import com.cea.papyrus.core.editor.CommandStackUtils;
import com.cea.papyrus.core.editor.ModelManager;
import com.cea.papyrus.core.editpart.IUMLElementEditPart;
import com.cea.papyrus.core.helper.ModelerHelper;
import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.diagraminterchange2.di2.Di2Factory;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.DiagramElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;
import com.cea.papyrus.profile.ImageManager;
import com.cea.papyrus.profile.tree.objects.PropertyTreeObject;
import com.cea.papyrus.profile.tree.objects.StereotypeTreeObject;
import com.cea.papyrus.profile.ui.composites.DecoratedTreeComposite;


//TODO: Auto-generated Javadoc
/**
 * The Class StereotypeComposite.
 */
public class StereotypeComposite extends com.cea.papyrus.profile.ui.composites.StereotypeComposite {

	/**
	 * The selection.
	 */
	protected ISelection selection;

	/**
	 * The model manager.
	 */
	private ModelManager modelManager;

	/**
	 * The diagram element.
	 */
	protected DiagramElement diagramElement=null;

	/**
	 * The property composite.
	 */
	protected PropertyComposite propertyComposite;

	/**
	 * button that displays the element.
	 */
	protected Button displayButton;

	/**
	 * Listener for the display button.
	 */
	protected MouseListener displayButtonListener;

	/**
	 * The Constant IMG_DISPLAY.
	 */
	private static final Image IMG_DISPLAY	= PapyrusPlugin.getImage(PapyrusPlugin.PAPYRUS_ICONS_16x16+"ConsoleView_16x16.gif");


	/**
	 * The Constructor.
	 * 
	 * @param parent the parent
	 */
	public StereotypeComposite(Composite parent){
		super(parent);
		this.setBackground(JFaceColors.getBannerBackground(parent.getDisplay()));
		this.displayButtonListener = new DisplayButtonListener();
	}

	/**
	 * Creates the content.
	 * 
	 * @param parent the parent
	 * @param factory the factory
	 * 
	 * @return the composite
	 */
	@Override
	public Composite createContent(Composite parent, TabbedPropertySheetWidgetFactory factory) {
		super.createContent(parent, factory);

		FormData data;
		displayButton = factory.createButton(this, "",  SWT.PUSH);
		displayButton.setVisible(true);
		displayButton.setImage(IMG_DISPLAY);
		displayButton.setToolTipText("Display selected stereotype for the element in the diagram");
		data = new FormData();
		// data.top   = new FormAttachment(addButton, ITabbedPropertyConstants.HSPACE);
		data.top  = new FormAttachment(0, 0);
		data.right = new FormAttachment(downButton, -ITabbedPropertyConstants.HSPACE);
		displayButton.setLayoutData(data);
		displayButton.addMouseListener(displayButtonListener);

		// Replace label and content providers in treeViewers
		treeViewer.setContentProvider(new ProfileElementWithDisplayContentProvider(diagramElement));
		treeViewer.setLabelProvider(new ProfileElementWithDisplayLabelProvider());

		refresh();
		return this;
	}


	/**
	 * Sets the model manager.
	 * 
	 * @param modelManager the model manager
	 */
	public void setModelManager(ModelManager modelManager){
		this.modelManager=modelManager;
	}

	/**
	 * Sets the selection.
	 * 
	 * @param selection the selection
	 */
	public void setSelection(ISelection selection){
		this.selection=selection;
	}

	/**
	 * Sets the diagram element.
	 * 
	 * @param diagramElement the diagram element
	 */
	public void setDiagramElement(DiagramElement diagramElement){
		this.diagramElement=diagramElement;
		((ProfileElementWithDisplayContentProvider) treeViewer.getContentProvider()).setDiagramElement(diagramElement);
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.profile.ui.composites.StereotypeComposite#refresh()
	 */
	@Override
	public void refresh() {
		super.refresh();
		if(diagramElement == null) {
			displayButton.setEnabled(false);
			displayButton.setToolTipText("Stereotypes can only be displayed for elements with graphical representation. Currently edited element is a non graphical element. (example: an element selected in the outline is not a graphical element)");
		} else {
			// button should be enabled only if a stereotype is selected, but it requires a listener on the tree selection
			displayButton.setEnabled(true);
			displayButton.setToolTipText("Display selected stereotype for the currently selected element in the diagram");
		}
	}

	/**
	 * Sets the property composite associated to this stereotype composite
	 * @param propertyComposite the composite associated to this stereotype composite used for stereotype property display.
	 */
	public void setPropertyComposite(PropertyComposite propertyComposite){
		this.propertyComposite=propertyComposite;
	}

	/**
	 * Gets the selected.
	 * 
	 * @return the selected
	 */
	@Override
	public Element getSelected() {
		Object input = ((IStructuredSelection) selection).getFirstElement();
		if(input instanceof IUMLElementEditPart) {
			return ((IUMLElementEditPart) input).getUmlElement();
		}
		else
			return null;
	}


	private IUMLElementEditPart getSelectedEditPart() {
		Object input = ((IStructuredSelection) selection).getFirstElement();
		if(input instanceof IUMLElementEditPart) {
			return (IUMLElementEditPart) input;
		}
		else
			return null;
	}

	/**
	 * Touch model.
	 * 
	 * @param elt the elt
	 */
	protected void touchModel(Element elt){
		if(elt instanceof NamedElement){
			((NamedElement)elt).setName(((NamedElement)elt).getName());
		} else {
			if(elt.getNearestPackage() != null) {
				elt.getNearestPackage().setName(elt.getNearestPackage().getName());
			}
		}
	}


	/**
	 * Adds the button pressed.
	 */
	@Override
	public void addButtonPressed() {
		super.addButtonPressed();
		touchModel(getSelected());
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.profile.ui.composites.StereotypeComposite#removeButtonPressed()
	 */
	@Override
	public void removeButtonPressed() {
		// tries to get the active command stack. If not possible, simply calls super method.
		CommandStack stack = getCommandStack();
		if(stack != null) {
			Command command = new PapyrusCommand() {
				@Override
				public void execute() {
					superRemoveButton();
				}
			};
			stack.execute(command);
		}
		else {
			superRemoveButton();
		}
		touchModel(getSelected());
	}

	/**
	 * Calls super method
	 */
	private void superRemoveButton() {
		super.removeButtonPressed();
	}

	/**
	 * Selection changed.
	 * 
	 * @param event the event
	 */
	@Override
	public void selectionChanged(SelectionChangedEvent event) {
		if (event == null) {
			propertyComposite.setInput(null);
			return;
		}

		IStructuredSelection structSelection = (IStructuredSelection) event.getSelection();
		Object selection = structSelection.getFirstElement();

		if (selection instanceof PropertyTreeObject) {
			propertyComposite.setInput((PropertyTreeObject) selection);
		} else {
			propertyComposite.setInput(null);
		}
	}


	/**
	 * Display button pressed.
	 */
	public void displayButtonPressed(){
		// If nothing selected : abort
		if (getTree().getSelection().length < 1) {
			return;
		}

		// bugfix: a selected element is not necessary a diagram element (ex: selection in the outline)
		if(getDiagramElement() == null) {
			return;
		}

		TreeItem[] treeSelection = getTree().getSelection();
		for(int i=0; i<treeSelection.length; i++){
			//stereotypesTree.getSelection()[0]
			if(treeSelection[i].getData() instanceof StereotypeTreeObject) {
				Stereotype stereo = ((StereotypeTreeObject)treeSelection[i].getData()).getStereotype();

				if(treeSelection[i].getImage().equals(ImageManager.IMG_STEREOTYPE)) {
					addStereotypeDisplay(stereo);
				}
				else {
					// if we remove the stereotype we have to remove its properties also
					removeStereotypePropertiesDisplay(stereo);
					removeStereotypeDisplay(stereo);
				}
			}

			else if(treeSelection[i].getData() instanceof PropertyTreeObject) {
				Property prop = ((PropertyTreeObject)treeSelection[i].getData()).getProperty();

				if(treeSelection[i].getImage().equals(ImageManager.IMG_STEREOTYPEDISPLAYED)) {
					removeStereotypePropertyDisplay(prop, ((StereotypeTreeObject)(treeSelection[i].getParentItem().getData())).getStereotype());
				}

				else {
					//if the stereotype owning the property we want to display is not displayed, we display it!
					if(!isInStereotypeDisplay(((StereotypeTreeObject)(treeSelection[i].getParentItem().getData())).getStereotype())) {
						addStereotypeDisplay(((StereotypeTreeObject)(treeSelection[i].getParentItem().getData())).getStereotype());
					}
					addStereotypePropertyDisplay(prop, ((StereotypeTreeObject)(treeSelection[i].getParentItem().getData())).getStereotype());
				}
			}

		}

		// Refresh
		buildStereotypeDisplay(getElement().getAppliedStereotypes());
		refresh();
		touchModel(getSelected());
	}

	/*
	 * (non-Javadoc)
	 * @see com.cea.papyrus.profile.ui.composites.DecoratedTreeComposite#disposeListeners()
	 */
	/**
	 * 
	 */
	public void disposeListeners(){
		super.disposeListeners();
		if(displayButton!=null && !displayButton.isDisposed())
			displayButton.removeMouseListener(displayButtonListener);
	}


	/**
	 * Listener for the Display Button
	 * Specific behavior is implemented in {@link DecoratedTreeComposite#downButtonPressed()}.
	 * 
	 * @author Chokri Mraidha
	 */
	private class DisplayButtonListener implements MouseListener {

		/**
		 * Mouse double click.
		 * 
		 * @param e the e
		 */
		public void mouseDoubleClick(MouseEvent e) {
			// do nothing
		}

		/**
		 * Mouse down.
		 * 
		 * @param e the e
		 */
		public void mouseDown(MouseEvent e) {
			// do nothing
		}

		/**
		 * Mouse up.
		 * 
		 * @param e the e
		 */
		public void mouseUp(MouseEvent e) {
			TreeItem[] treeItems = getTree().getSelection();
			displayButtonPressed();
			refresh();
			// Keep selection
			if(! getTree().isDisposed()){
				getTree().setSelection(treeItems);
			}
		}
	}



	/** Stereotype display operations **********************/


	/**
	 * returns the selected DiagramElement.
	 * 
	 * @return the diagram element
	 */
	public DiagramElement getDiagramElement() {
		return diagramElement;
	}

	/**
	 * Add a stereotype to the list of stereotypes to display.
	 * 
	 * @param st the stereotype to add
	 */
	@Override
	protected void addStereotypeDisplay(Stereotype st) {
		// bugfix: a selected element is not necessary a diagram element (ex: selection in the outline)
		if(getDiagramElement() == null) {
			return;
		}

		// property to add creation
		com.cea.papyrus.diagraminterchange2.di2.Property property = Di2Factory.eINSTANCE.createProperty();
		property.setKey(ModelerHelper.PROPERTY_STEREOTYPE_DISPLAY);
		property.setValue(st.getQualifiedName());

		// add the property
		getDiagramElement().getProperty().add(property);

	}

	/**
	 * Remove a stereotype from the list of stereotypes to display.
	 * 
	 * @param st the stereotype to remove
	 */
	@Override
	protected void removeStereotypeDisplay(Stereotype st){
		// bugfix: a selected element is not necessary a diagram element (ex: selection in the outline)
		if(getDiagramElement() == null) {
			return;
		}


		// set of diagrams
		Iterator<Diagram> diagramIter = modelManager.getDiagrams().iterator();

		// set of graphElements associated to the uml element we unapply stereotype from 
		ArrayList<GraphElement> filteredGraphElements = new ArrayList<GraphElement>();


		while(diagramIter.hasNext()) {
			Diagram diag = diagramIter.next();

			// list of graphElements of a diagram
			ArrayList<GraphElement> diagramGraphElements = new ArrayList<GraphElement>();
			diagramGraphElements = LookForElement.getAllGraphElements(diag, diagramGraphElements);


			// filter diagramsGraphElements in order to hold graphElements associated to the uml element
			// we unapply the stereotype from
			Iterator<GraphElement> geIter = diagramGraphElements.iterator();
			while(geIter.hasNext()){
				GraphElement ge = geIter.next();

				// if the diagram element property list is empty, it has not have any stereotype
				if(ge.getProperty().size()!=0) {
					if(getElement().equals(((Uml1SemanticModelBridge)ge.getSemanticModel()).getElement())) {
						filteredGraphElements.add(ge);
					}
				}
			}
		}


		Iterator stIter = filteredGraphElements.iterator();
		while(stIter.hasNext()){
			DiagramElement currentDE = (DiagramElement)stIter.next();
			EList propList = currentDE.getProperty();
			ArrayList<com.cea.papyrus.diagraminterchange2.di2.Property> tempPropList = new ArrayList<com.cea.papyrus.diagraminterchange2.di2.Property>();
			for(int i=0; i<propList.size(); i++) {
				tempPropList.add(i, ((com.cea.papyrus.diagraminterchange2.di2.Property)propList.get(i)));
			}

			Iterator iter = tempPropList.iterator();

			while(iter.hasNext()) {
				com.cea.papyrus.diagraminterchange2.di2.Property prop = (com.cea.papyrus.diagraminterchange2.di2.Property)iter.next();
				if(prop.getKey().equals(ModelerHelper.PROPERTY_STEREOTYPE_DISPLAY)) {
					if(prop.getValue().equals(st.getQualifiedName())) {
						propList.remove(prop);
					}
				}
			}
		}
	}

	/**
	 * Checks wether the stereotype is in the display list or not.
	 * 
	 * @param st to check out
	 * 
	 * @return true if the stereotype is in the display list, false otherwise
	 */
	@Override
	protected Boolean isInStereotypeDisplay(Stereotype st) {
		// bugfix: a selected element is not necessary a diagram element (ex: selection in the outline)
		if(getDiagramElement() == null) {
			return false;
		}

		EList propList = getDiagramElement().getProperty();
		Iterator iter = propList.iterator();
		while(iter.hasNext()) {
			com.cea.papyrus.diagraminterchange2.di2.Property prop = (com.cea.papyrus.diagraminterchange2.di2.Property)iter.next();
			if(prop.getKey().equals(ModelerHelper.PROPERTY_STEREOTYPE_DISPLAY)) {
				if(prop.getValue().equals(st.getQualifiedName())) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * This method builds the stereotypes to display list in order
	 * to reorder stereotypes display order according to stereotypes
	 * application order.
	 * 
	 * @param appliedStereotypes the applied stereotypes
	 */
	@Override
	protected void buildStereotypeDisplay(EList appliedStereotypes) {
		// bugfix: a selected element is not necessary a diagram element (ex: selection in the outline)
		if(getDiagramElement() == null) {
			return;
		}

		EList propList = getDiagramElement().getProperty();

		// temporary list to avoid concurrent access to propList
		ArrayList tempPropList = new ArrayList();

		// list containing stereotype display properties of the current graphElement
		ArrayList<com.cea.papyrus.diagraminterchange2.di2.Property> stereoDiplayList = new ArrayList<com.cea.papyrus.diagraminterchange2.di2.Property>();

		// copy propList to tempPropList
		for(int i=0; i<propList.size(); i++){
			tempPropList.add(propList.get(i));
		}

		// build stereoDiplayList and remove stereotype display properties form propList
		Iterator<com.cea.papyrus.diagraminterchange2.di2.Property> iter = tempPropList.iterator();
		while(iter.hasNext()){
			com.cea.papyrus.diagraminterchange2.di2.Property prop = iter.next();
			if(prop.getKey().equals(ModelerHelper.PROPERTY_STEREOTYPE_DISPLAY)) {
				stereoDiplayList.add(prop);
				propList.remove(prop);
			}
		}

		// add stereotype display properties in the appropriate order
		Iterator<Stereotype> stereoIter = appliedStereotypes.iterator();
		while(stereoIter.hasNext()) {
			String stereoName = stereoIter.next().getQualifiedName();
			Iterator<com.cea.papyrus.diagraminterchange2.di2.Property> propIter = stereoDiplayList.iterator();
			while(propIter.hasNext()) {
				com.cea.papyrus.diagraminterchange2.di2.Property prop = propIter.next();
				if(prop.getValue().equals(stereoName)) {
					propList.add(prop);
				}
			}
		}
	}


	/** Stereotype properties values display operations **********************/

	/**
	 * Adds a stereotype property to the list of properties to display for a diagram element.
	 * 
	 * @param owner the owner
	 * @param prop the stereotype property to add
	 */
	private void addStereotypePropertyDisplay(Property prop, Stereotype owner) {
		// bugfix: a selected element is not necessary a diagram element (ex: selection in the outline)
		if(getDiagramElement() == null) {
			return;
		}

		// property to add creation
		com.cea.papyrus.diagraminterchange2.di2.Property property = Di2Factory.eINSTANCE.createProperty();
		property.setKey(ModelerHelper.PROPERTY_STEREOTYPE_PROPERTY_VALUES_DISPLAY);
		property.setValue(owner.getQualifiedName()+"::"+prop.getName());

		//Debug.debug(this, "OWNER::ADD = "+property.getValue(), channel);


		// add the property
		getDiagramElement().getProperty().add(property);

		// command creation: display stereotype properties in active diagram
		Command command = new StereotypePropertiesDisplayCreateCommand(getSelectedEditPart(), getDiagramElement(), getDiagramElement().getContainer());

		// getCommandStack
		CommandStack stack = CommandStackUtils.getCommandStack();

		Assert.isNotNull(stack, "Impossible to adapt current editor into a CommandStackUtils");
		stack.execute(command);

	}

	/**
	 * Removes a stereotype property from the list of properties to display for a diagram element.
	 * 
	 * @param owner the owner
	 * @param property the stereotype property to remove
	 */
	protected void removeStereotypePropertyDisplay(Property property, Stereotype owner){
		// bugfix: a selected element is not necessary a diagram element (ex: selection in the outline)
		if(getDiagramElement() == null) {
			return;
		}


		//Debug.debug(this, "OWNER::REMOVE = "+owner.getQualifiedName(), channel);

		EList propList = getDiagramElement().getProperty();
		ArrayList<com.cea.papyrus.diagraminterchange2.di2.Property> tempPropList = new ArrayList<com.cea.papyrus.diagraminterchange2.di2.Property>();
		for(int i=0; i<propList.size(); i++) {
			tempPropList.add(i, ((com.cea.papyrus.diagraminterchange2.di2.Property)propList.get(i)));
		}

		Iterator iter = tempPropList.iterator();

		while(iter.hasNext()) {
			com.cea.papyrus.diagraminterchange2.di2.Property prop = (com.cea.papyrus.diagraminterchange2.di2.Property)iter.next();
			if(prop.getKey().equals(ModelerHelper.PROPERTY_STEREOTYPE_PROPERTY_VALUES_DISPLAY)) {
				if(prop.getValue().equals(owner.getQualifiedName()+"::"+property.getName())) {
					propList.remove(prop);
				}
			}
		}

		// count the number of stereotype properties to display
		Iterator<com.cea.papyrus.diagraminterchange2.di2.Property> iterProp = getDiagramElement().getProperty().iterator();
		int count=0;
		while(iterProp.hasNext()){
			com.cea.papyrus.diagraminterchange2.di2.Property diProp = iterProp.next();
			if(diProp.getKey().equals(ModelerHelper.PROPERTY_STEREOTYPE_PROPERTY_VALUES_DISPLAY)) {
				count++;
			}
		}

		// is there some properties to display?
		if(count==0) {
			Command command = DiagramElementDeleteCommandFactory.eINSTANCE.createCommand((GraphNode)LookForElement.getSemanticChildWithoutUmlSemanticAndNoContent(getDiagramElement()));

			// getCommandStack
			CommandStack stack = CommandStackUtils.getCommandStack();

			Assert.isNotNull(stack, "Impossible to adapt current editor into a CommandStackUtils");
			stack.execute(command);
		}

	}

	/**
	 * Checks wether the stereotype property is in the display list or not.
	 * 
	 * @param owner the owner
	 * @param property to check out
	 * 
	 * @return true if the stereotype property is in the display list, false otherwise
	 */
	private Boolean isInStereoPropertyDisplay(Property property, Stereotype owner) {
		// bugfix: a selected element is not necessary a diagram element (ex: selection in the outline)
		if(getDiagramElement() == null) {
			return false;
		}

		EList propList = getDiagramElement().getProperty();
		Iterator iter = propList.iterator();
		while(iter.hasNext()) {
			com.cea.papyrus.diagraminterchange2.di2.Property prop = (com.cea.papyrus.diagraminterchange2.di2.Property)iter.next();
			if(prop.getKey().equals(ModelerHelper.PROPERTY_STEREOTYPE_PROPERTY_VALUES_DISPLAY)) {
				if(prop.getValue().equals(owner.getQualifiedName()+"::"+property.getName())) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Removes all properties display of the given stereotype.
	 * 
	 * @param stereotype the stereotype which we remove all properties from diagram element
	 */
	@Override
	protected void removeStereotypePropertiesDisplay(Stereotype stereotype){
		Iterator<Property> stereoPropIter = stereotype.getAllAttributes().iterator();
		while(stereoPropIter.hasNext()){
			Property currentProp = stereoPropIter.next();
			if(isInStereoPropertyDisplay(currentProp, stereotype)) {
				removeStereotypePropertyDisplay(currentProp, stereotype);
			}
		}
	}
}
